﻿using System;
using System.Globalization;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework;

using System.Collections.Generic;
using SilverBullet.Graphics;

namespace Microsoft.Xna.Framework.Graphics
{
	/// <summary>
	/// Performs primitive-based rendering, creates resources, handles system-level variables, adjusts gamma ramp levels, and creates shaders.
	/// </summary>
    public class GraphicsDevice : IDisposable
    {
		private GraphicsAdapter currentAdapter;
		private Viewport currentViewport;
		private bool isDisposed;
		private PresentationParameters presentationParameters;

        SolidColorBrush backgroundBrush;
        Color background;
        Canvas root;
		private int currentRenderTargetCount;
        RenderTargetBinding[] _renderTargets;
        static internal List<RenderTarget> _allRenderTargets = new List<RenderTarget>();
		private RenderTarget2D backBuffer;
		private TextureCollection textures;

		#region Events
		/// <summary>
		/// Occurs after a GraphicsDevice is reset, allowing an application to recreate all resources.
		/// </summary>
        public event EventHandler<EventArgs> DeviceReset;

		/// <summary>
		/// Occurs when a GraphicsDevice is resetting, allowing the application to cancel the default handling of the reset.
		/// </summary>
		public event EventHandler<EventArgs> DeviceResetting;

		/// <summary>
		/// Occurs when a GraphicsDevice is about to be lost (for example, immediately before a reset).
		/// </summary>
        public event EventHandler<EventArgs> DeviceLost;

		/// <summary>
		/// Occurs when Dispose is called or when this object is finalized and collected by the garbage collector of the Microsoft .NET common language runtime.
		/// </summary>
		public event EventHandler<EventArgs> Disposing;

		/// <summary>
		/// Occurs when a resource is created.
		/// </summary>
        public event EventHandler<ResourceCreatedEventArgs> ResourceCreated;

		/// <summary>
		/// Occurs when a resource is destroyed.
		/// </summary>
        public event EventHandler<ResourceDestroyedEventArgs> ResourceDestroyed;
		#endregion

		public SamplerStateCollection SamplerStates = new SamplerStateCollection();
        public static bool BitmapCacheEnabled;
        public static double RenderAtScale;

		private const int MaxRenderTargets = 1;

		#region Properties
		/// <summary>
		/// Gets the graphics adapter.
		/// </summary>
		public GraphicsAdapter Adapter
		{
			get { return currentAdapter; }
		}

		/// <summary>
		/// Retrieves the display mode's spatial resolution, color resolution, and refresh frequency.
		/// </summary>
		public DisplayMode DisplayMode
		{
			get { return currentAdapter.CurrentDisplayMode; }
		}

		internal bool IsDeviceLost { get; private set; }

		/// <summary>
		/// Gets a value that indicates whether the object is disposed.
		/// </summary>
		public bool IsDisposed
		{
			get { return isDisposed; }
		}

		/// <summary>
		/// Returns the collection of textures that have been assigned to the texture stages of the device.
		/// </summary>
		public TextureCollection Textures
		{
			get { return textures; }
		}

		/// <summary>
		/// Gets or sets a viewport identifying the portion of the render target to receive draw calls.
		/// </summary>
		public Viewport Viewport
		{
			get
			{
				//RenderTarget t = GetCurrentRenderTarget();
				//if (t != null)
				//{
				//    return t._viewport;
				//}
				//else
				//{
				//    return new Viewport()
				//    {
				//        Width = GraphicsDeviceManager.PreferredBackBufferWidth,
				//        Height = GraphicsDeviceManager.PreferredBackBufferHeight
				//    };
				//}
				return this.currentViewport;
			}
			set
			{
				if (((value.X < 0) || (value.Y < 0)) || ((value.Width <= 0) || (value.Height <= 0)))
				{
					throw new ArgumentException(FrameworkResources.ViewportInvalid, "value");
				}

				currentViewport = value;
				//GetCurrentRenderTarget()._viewport = value;
			}
		}
		#endregion

		#region Constructor
		/// <summary>
		/// Creates an instance of this object.
		/// </summary>
		/// <param name="adapter">The display adapter.</param>
		/// <param name="graphicsProfile">The graphics profile.</param>
		/// <param name="presentationParameters">The presentation options.</param>
		public GraphicsDevice(GraphicsAdapter adapter, GraphicsProfile graphicsProfile, PresentationParameters presentationParameters)
        {
			if (presentationParameters == null)
			{
				throw new ArgumentNullException("presentationParameters", FrameworkResources.NullNotAllowed);
			}
			if (adapter == null)
			{
				throw new ArgumentNullException("adapter", FrameworkResources.NullNotAllowed);
			}

			this.currentAdapter = adapter;
			//this.RenderState = new RenderState();
			this.presentationParameters = presentationParameters;
			this.PresentationParameters.BackBufferFormat = SurfaceFormat.Color;
			this.textures = new TextureCollection();
			this.CreateDevice(adapter, presentationParameters);
        }

		private void CreateDevice(GraphicsAdapter adapter, PresentationParameters presentationParameters)
		{
			this._renderTargets = new RenderTargetBinding[MaxRenderTargets];

			backBuffer = new RenderTarget2D(this, presentationParameters.BackBufferWidth, presentationParameters.BackBufferHeight, false, presentationParameters.BackBufferFormat, presentationParameters.DepthStencilFormat);

			// Bind the backbuffer to be the first RenderTarget
			this._renderTargets[0] = new RenderTargetBinding(backBuffer);
			this.currentRenderTargetCount = 1;
		}
		#endregion

		public void CleanupTexture(Texture2D texture)
        {
			//foreach (RenderTarget2D target in _allRenderTargets)
			//{
			//    if (target._renderer is CanvasRenderer)
			//    {
			//        (target._renderer as CanvasRenderer).DisposeTextureAssets(texture);
			//    }
			//}
        }

		/// <summary>
		/// Gets a render target surface.
		/// </summary>
		public RenderTargetBinding[] GetRenderTargets()
		{
			RenderTargetBinding[] destinationArray = new RenderTargetBinding[currentRenderTargetCount];
			Array.Copy(this._renderTargets, destinationArray, currentRenderTargetCount);
			return destinationArray;
		}

        internal Canvas Root
        {
            get
            {
                return root;
            }
            set
            {
                root = value;
                background = Color.CornflowerBlue;
                backgroundBrush = new SolidColorBrush(background.ToSilverlightColor());
                root.Background = backgroundBrush;
				//_renderTargets = new RenderTarget[1];
				//RenderTarget2D target = new RenderTarget2D(this, GraphicsDeviceManager.PreferredBackBufferWidth, GraphicsDeviceManager.PreferredBackBufferHeight, 1, PresentationParameters.BackBufferFormat, RenderTargetUsage.DiscardContents);
				//backBuffer = target;
				//_allRenderTargets.Add(backBuffer);
				//root.Children.Add(backBuffer._renderer.Root);
            }
        }

		/// <summary>
		/// Clears resource buffers.
		/// </summary>
		/// <param name="color">Set this color value in all buffers.</param>
        public void Clear(Color color)
        {
			Clear(ClearOptions.Target, color, 1f, 0);

			//RenderTarget t = _renderTargets[0];
			//if (t == null)
			//{
			//    t = backBuffer;
			//}
			//t._renderer.Clear(color);
        }

		/// <summary>
		/// Clears resource buffers.
		/// </summary>
		/// <param name="options">Options for clearing a buffer.</param>
		/// <param name="color">Set this color value in the buffer.</param>
		/// <param name="depth">Set this depth value in the buffer.</param>
		/// <param name="stencil">Set this stencil value in the buffer.</param>
		public void Clear(ClearOptions options, Color color, float depth, int stencil)
		{
			switch (options)
			{
				case ClearOptions.Target:
					//(this._renderTargets[0].RenderTarget.Texture as Texture2D).backingStore.Clear(color);
					break;
				case ClearOptions.DepthBuffer:
					//! TODO: implement
					break;
				case ClearOptions.Stencil:
					//! TODO: implement
					break;
			}
		}

		/// <summary>
		/// Clears resource buffers.
		/// </summary>
		/// <param name="options">Options for clearing a buffer.</param>
		/// <param name="color">Set this four-component color value in the buffer.</param>
		/// <param name="depth">Set this depth value in the buffer.</param>
		/// <param name="stencil">Set this stencil value in the buffer.</param>
		public void Clear(ClearOptions options, Vector4 color, float depth, int stencil)
		{
			Clear(options, new Color(color), depth, stencil);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="primitiveType"></param>
		/// <param name="baseVertex"></param>
		/// <param name="minVertexIndex"></param>
		/// <param name="numVertices"></param>
		/// <param name="startIndex"></param>
		/// <param name="primitiveCount"></param>
		public void DrawIndexedPrimitives(PrimitiveType primitiveType, int baseVertex, int minVertexIndex, int numVertices, int startIndex, int primitiveCount)
		{
			if (numVertices <= 0)
			{
				throw new ArgumentOutOfRangeException("numVertices", FrameworkResources.NumberVerticesMustBeGreaterZero);
			}
			if (primitiveCount <= 0)
			{
				throw new ArgumentOutOfRangeException("primitiveCount", FrameworkResources.MustDrawSomething);
			}
			if (primitiveCount > 0xffff)
			{
				throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, FrameworkResources.ProfileMaxPrimitiveCount, GraphicsProfile.Reach, 0xffff));
			}

			//! TODO: if possible, implement
		}

		/// <summary>
		/// Gets the presentation parameters associated with this graphics device.
		/// </summary>
        public PresentationParameters PresentationParameters
        {
			get { return presentationParameters; }
        }

		private void SetRenderTargets(int renderTargetCount, params RenderTargetBinding[] pBindings)
		{
		}

		protected void Dispose(bool disposing)
		{
			if (disposing)
			{
				for (int i = 0; i < textures.Count; i++)
				{
					textures[i].Dispose();
				}
			}
		}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Presents the display with the contents of the next buffer in the sequence of back buffers owned by the GraphicsDevice.
		/// </summary>
		public void Present()
		{
			Present(null, null, IntPtr.Zero);
		}

		/// <summary>
		/// Specifies the window target for a presentation and presents the display with the contents of the next buffer in the sequence of back buffers owned by the GraphicsDevice.
		/// </summary>
		/// <param name="sourceRectangle">The source rectangle. If null, the entire source surface is presented. If the rectangle exceeds the source surface, the rectangle is clipped to the source surface.</param>
		/// <param name="destinationRectangle">The destination rectangle, in window client coordinates. If null, the entire client area is filled. If the rectangle exceeds the destination client area, the rectangle is clipped to the destination client area.</param>
		/// <param name="overrideWindowHandle">Destination window containing the client area that is the target for this presentation. If not specified, this is DeviceWindowHandle.</param>
        public void Present(Rectangle? sourceRectangle, Rectangle? destinationRectangle, IntPtr overrideWindowHandle)
        {
            
        }

		/// <summary>
		/// Resets the presentation parameters for the current GraphicsDevice.
		/// </summary>
		public void Reset()
		{
			Reset(this.presentationParameters, this.Adapter);
		}

		/// <summary>
		/// Resets the current GraphicsDevice with the specified PresentationParameters.
		/// </summary>
		/// <param name="presentationParameters">Describes the new presentation parameters. This value cannot be null.</param>
		public void Reset(PresentationParameters presentationParameters)
		{
			Reset(presentationParameters, this.Adapter);	
		}

		/// <summary>
		/// Resets the specified Reset with the specified presentation parameters.
		/// </summary>
		/// <param name="presentationParameters">Describes the new presentation parameters. This value cannot be null.</param>
		/// <param name="graphicsAdapter">The graphics device being reset.</param>
		public void Reset(PresentationParameters presentationParameters, GraphicsAdapter graphicsAdapter)
		{
			if (presentationParameters == null)
			{
				throw new ArgumentNullException("presentationParameters", FrameworkResources.NullNotAllowed);
			}
			if (graphicsAdapter == null)
			{
				throw new ArgumentNullException("graphicsAdapter", FrameworkResources.NullNotAllowed);
			}

			EventHandler<EventArgs> handler = this.DeviceResetting;
			if (handler != null)
			{
				handler(this, EventArgs.Empty);
			}

			this.presentationParameters = presentationParameters;
			//Root.Children.Remove(backBuffer._renderer.Root);
			//_allRenderTargets.Remove(backBuffer);
			//backBuffer = new RenderTarget2D(this, Viewport.Width, Viewport.Height, 1, PresentationParameters.BackBufferFormat, RenderTargetUsage.DiscardContents);
			//Root.Children.Add(backBuffer._renderer.Root);

			handler = this.DeviceReset;
			if (handler != null)
			{
				handler(this, EventArgs.Empty);
			}
		}

        public static void SetGlobalBitmapCache(bool cached, double renderAtScale)
        {
			//foreach (RenderTarget target in _allRenderTargets)
			//{
			//    RenderTarget2D target2D = target as RenderTarget2D;
			//    if (target2D != null)
			//    {
			//        target2D.BitmapCacheEnabled = cached;
			//        target2D.RenderAtScale = renderAtScale;
			//    }
			//}
        }

		public void SetRenderTarget(RenderTarget2D renderTarget)
		{
			if (renderTarget != null)
			{
				RenderTargetBinding pBindings = new RenderTargetBinding(renderTarget);
				this.SetRenderTargets(1, pBindings);
			}
			else
			{
				this.SetRenderTargets(0, null);
			}
		}

		public void SetRenderTarget(RenderTargetCube renderTarget, CubeMapFace cubeMapFace)
		{
			if (renderTarget != null)
			{
				RenderTargetBinding pBindings = new RenderTargetBinding(renderTarget, cubeMapFace);
				this.SetRenderTargets(1, pBindings);
			}
			else
			{
				this.SetRenderTargets(0, null);
			}
		}

		public void SetRenderTargets(params RenderTargetBinding[] renderTargets)
		{
			if ((renderTargets != null) && (renderTargets.Length > 0))
			{
				this.SetRenderTargets(renderTargets.Length, renderTargets);
			}
			else
			{
				this.SetRenderTargets(0, null);
			}
		}

		/// <summary>
		/// Sets or binds a vertex buffer to the device.
		/// </summary>
		/// <param name="vertexBuffer">A vertex buffer.</param>
		public void SetVertexBuffer(VertexBuffer vertexBuffer)
		{
		}

		public GraphicsProfile GraphicsProfile { get; set; }
	}
}
